package com.techstar.ats.utils;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;

import io.netty.channel.nio.NioEventLoopGroup;

@Slf4j
public final class SharedUtils {
	private static final Runtime RUNTIME = Runtime.getRuntime();

	private static NioEventLoopGroup EVENT_LOOP;
	private static ExecutorService EXECUTOR_SERVICE;
	private static ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE;

	public static synchronized NioEventLoopGroup sharedEventLoop() {
		if(EVENT_LOOP == null) {
			ThreadFactory threadFactory = new ThreadFactory() {
				private final AtomicLong threadNumber = new AtomicLong(0L);

				@Override
				public Thread newThread(Runnable r) {
					Thread thread = new Thread(r, "netty-event-loop-" + threadNumber.getAndIncrement());
					thread.setDaemon(true);
					return thread;
				}
			};

			EVENT_LOOP = new NioEventLoopGroup(RUNTIME.availableProcessors() * 2, threadFactory);
		}

		return EVENT_LOOP;
	}

	public static synchronized ExecutorService sharedExecutor() {
		if(EXECUTOR_SERVICE == null) {
			ThreadFactory threadFactory = new ThreadFactory() {
				private final AtomicLong threadNumber = new AtomicLong(0L);

				@Override
				public Thread newThread(Runnable r) {
					Thread thread = new Thread(r, "shared-thread-pool-" + threadNumber.getAndIncrement());
					thread.setDaemon(true);
					thread.setUncaughtExceptionHandler(
						(t, e) ->
							log.warn("Uncaught Exception on shared stack ExecutorService thread!", e)
					);
					return thread;
				}
			};

			EXECUTOR_SERVICE = Executors.newCachedThreadPool(threadFactory);
		}

		return EXECUTOR_SERVICE;
	}

	public static synchronized ScheduledExecutorService sharedScheduledExecutor() {
		if(SCHEDULED_EXECUTOR_SERVICE == null) {
			ThreadFactory threadFactory = new ThreadFactory() {
				private final AtomicLong threadNumber = new AtomicLong(0L);

				@Override
				public Thread newThread(Runnable r) {
					Thread thread = new Thread(r, "shared-scheduled-executor-" + threadNumber.getAndIncrement());
					thread.setDaemon(true);
					thread.setUncaughtExceptionHandler(
						(t, e) ->
							log.warn("Uncaught Exception on shared stack ScheduledExecutorService thread!", e)
					);
					return thread;
				}
			};

			ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
				RUNTIME.availableProcessors(), threadFactory
			);

			executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);

			SCHEDULED_EXECUTOR_SERVICE = executor;
		}

		return SCHEDULED_EXECUTOR_SERVICE;
	}

	public static synchronized void releaseSharedResources() {
		releaseSharedResources(5, TimeUnit.SECONDS);
	}

	public static synchronized void releaseSharedResources(long timeout, TimeUnit unit) {
		if(EVENT_LOOP != null) {
			try {
				EVENT_LOOP.shutdownGracefully()
					.await(timeout, unit);
			} catch(InterruptedException e) {
				log.warn("Interrupted awaiting event loop shutdown.", e);
			}
			EVENT_LOOP = null;
		}

		if(EXECUTOR_SERVICE != null) {
			try {
				EXECUTOR_SERVICE.shutdown();
				EXECUTOR_SERVICE.awaitTermination(timeout, unit);
			} catch(InterruptedException e) {
				log.warn("Interrupted awaiting executor service shutdown.", e);
			}
			EXECUTOR_SERVICE = null;
		}

		if(SCHEDULED_EXECUTOR_SERVICE != null) {
			try {
				SCHEDULED_EXECUTOR_SERVICE.shutdown();
				SCHEDULED_EXECUTOR_SERVICE.awaitTermination(timeout, unit);
			} catch(InterruptedException e) {
				log.warn("Interrupted awaiting scheduled executor service shutdown.", e);
			}
			SCHEDULED_EXECUTOR_SERVICE = null;
		}
	}
}
